<?php

namespace app\webscoket;

use app\common\service\webscoket\CacheService;
use Swoole\Websocket\Frame;
use think\App;
use think\Config;
use think\Event;
use think\Request;
use think\facade\Log;
use think\response\Json;
use think\swoole\contract\websocket\HandlerInterface;
use think\swoole\Websocket;
use think\swoole\websocket\Room;
use app\webscoket\Room as NowRoom;
use think\facade\Cache;

class Manager extends Websocket  implements HandlerInterface
{

    /**
     * @var \Swoole\WebSocket\Server
     */
    protected $server;

    /**
     * @var Config
     */
    protected $config;

    /**
     * 定时器执行间隔(毫秒)
     * @var int
     */
    protected $interval = 2000;

    /**
     * @var Ping
     */
    protected $pingService;

    /**
     * @var int
     */
    protected $cache_timeout;

    /**
     * @var Response
     */
    protected $response;

    /**
     * @var \Redis
     */
    protected $cache;

    /**
     * @var NowRoom
     */
    protected $nowRoom;

    const USER_TYPE = ['admin', 'user'];

    /**
     * Manager constructor.
     * @param App $app
     * @param Server $server
     * @param Room $room
     * @param Event $event
     * @param Response $response
     * @param Ping $ping
     * @param \app\webscoket\Room $nowRoom
     */
    public function __construct(App $app, Websocket $server, Room $room, Event $event, Response $response, Ping $ping, NowRoom $nowRoom, Config $config)
    {
        parent::__construct($app, $room, $event);
        $this->response = $response;
        $this->pingService = $ping;
        $this->server = $server;
        $this->nowRoom = $nowRoom;
        $this->config = $config;
        $this->cache = CacheService::redisHandler();
        $this->nowRoom->setCache($this->cache);
        $this->cache_timeout = intval(app()->config->get('swoole.websocket.ping_timeout', 60000) / 1000) + 2;
    }
    /**
     * WebSocket 建立连接时对应的事件处理函数
     * @param \Swoole\WebSocket\Server $server
     * @param Request $request
     */
    public function onOpen(Request $request)
    {
        $fd = $this->server->getSender();
        try {
            $type = $request->get('type');
            $uid =0;
            switch ($type){
                case 'user':
                    $uid = member_id(true);
                    break;
                case 'admin':
                    $uid = user_id(true);
                    break;
            }
            if (!$uid || !in_array($type, self::USER_TYPE)) {
                Log::error("onOpen1关闭了");
                return $this->close();
            }
            $this->nowRoom->type($type);
            if ($uid) {
                $this->login($type, $uid, $fd);
            }
            $this->nowRoom->add($fd, $type, $uid);
            $this->pingService->createPing($fd, time(), $this->cache_timeout);
            //查看是否有订单
            if($type=='user'){
                $info = $this->nowRoom->get($fd);
                $res = $this->exec($type,'getOrder', [$fd, 'onOpen', $info, $this->response]);
                return $this->send($fd, $res);
            }else{
                $this->send($fd, $this->response->message('ping', ['now' => time()]));
            }
        } catch (\Exception $e) {
            $errmsg = $e->getMessage();
            $this->server->close();
            Log::error("onOpen2关闭了");
            return $this->send($fd, $this->response->fail(['msg'=>$errmsg]));
        }
    }
    public function login($type, $uid, $fd)
    {
        $key = '_ws_' . $type;
        $this->cache->sadd($key, $fd);
        $this->cache->sadd($key . $uid, $fd);
        $this->refresh($type, $uid);
    }
    /**
     * 刷新key
     * @param $type
     * @param $uid
     */
    public function refresh($type, $uid)
    {
        $key = '_ws_' . $type;
        $this->cache->expire($key, 1800);
        $this->cache->expire($key . $uid, 1800);
    }

    /**
     * 发送文本响应
     * @param $fd
     * @param Json $json
     * @return bool
     */
    public function send($fd, Json $json)
    {
        $this->pingService->createPing($fd, time(), $this->cache_timeout);
        try{
            $data = $json->getData();
            $data = is_array($data) ? json_encode($data) : $data;
            $this->server->push($data);
        } catch (\Throwable $e) {
            Log::error("send发送文本关闭了");
            return $this->server->close();
        }
    }
    /**
     * WebSocket 接受消息时对应的事件处理函数
     * @param Frame $frame
     */
    public function onMessage(Frame $frame)
    {
        $fd = $this->server->getSender();
        $info = $this->nowRoom->get($fd);
        $result = json_decode($frame->data, true) ?: [];
        if (!isset($result['type']) || !$result['type']) return true;
        if (!isset($info['type']) || !$info['type']) return true;
        $this->refresh($info['type'], $info['user_id']);

        if ($result['type'] == 'ping') {
            //查看是否有订单
            $res = $this->exec($info['type'],'getOrder', [$fd, 'onMessage', $info, $this->response]);
            return $this->send($fd, $res);
//            return $this->send($fd, $this->response->message('ping', ['now' => time()]));
        }
        $data = $result['data'] ?? [];
        $res = $this->exec($info['type'], $result['type'], [$fd, 'onMessage', $data, $this->response]);
        if ($res) return $this->send($fd, $res);
        return true;
    }

    /**
     * 执行事件调度
     * @param $type
     * @param $method
     * @param $result
     * @return null|Json
     */
    protected function exec($type, $method, $result)
    {
        if (!in_array($type, self::USER_TYPE)) {
            return null;
        }
        if (!is_array($result)) {
            return null;
        }
        /** @var Json $response */
        return $this->event->until('swoole.websocket.' . $type, [$method, $result, $this, $this->nowRoom]);
    }
    /**
     * WebSocket 断开连接时对应的事件处理函数
     * @param int $fd
     */
    public function onClose()
    {
        $fd = $this->server->getSender();
        $tabfd = (string)$fd;
        if ($this->nowRoom->exist($fd)) {
            $data = $this->nowRoom->get($tabfd);
            $this->logout($data['type'], $data['user_id'], $fd);
            $this->nowRoom->type($data['type'])->del($tabfd);
            $this->exec($data['type'], 'close', [$fd, null, $data, $this->response]);
        }
        $this->pingService->removePing($fd);
        $this->server->close();
        Log::error("onClose断开连接");
    }

    /**
     * 退出删除缓存
     * @Author: 易军辉
     * @DateTime:2024-08-19 10:47
     * @param $type
     * @param $uid
     * @param $fd
     */
    public function logout($type, $uid, $fd)
    {
        $key = '_ws_' . $type;
        $this->cache->srem($key, $fd);
        $this->cache->srem($key . $uid, $fd);
    }
    public function encodeMessage($message)
    {
        return $message;
    }

    /**
     * 批量发送
     * @Author: 易军辉
     * @DateTime:2024-08-19 10:55
     * @param $fds
     * @param $data
     * @param $exclude
     * @return mixed|true|null
     */
    public function pushing($fds, $data, $exclude = null)
    {
        if ($data instanceof Json) {
            $data = $data->getData();
        }
        $data = is_array($data) ? json_encode($data) : $data;
        $fds = is_array($fds) ? $fds : [$fds];
        foreach ($fds as $fd) {
            if (!$fd) {
                continue;
            }
            if ($exclude && is_array($exclude) && !in_array($fd, $exclude)) {
                continue;
            } elseif ($exclude && $exclude == $fd) {
                continue;
            }

            try{
                $this->server->to($fd)->push($data);
            } catch (\Throwable $e) {
                Log::error("pushing关闭");
                return $this->server->close();
            }

        }
        return true;
    }

    /**
     * 根据用户id获取fds
     * @Author: 易军辉
     * @DateTime:2024-08-19 10:58
     * @param int $userId
     * @return array
     */
    public static function getUserIdByFds(int $userId)
    {
        $toUserFd = [];
        foreach (['user','admin'] as $type) {
            $toUserFd = array_merge($toUserFd, self::getUserIdByFd($userId, $type) ?: []);
        }
        return array_merge(array_unique($toUserFd));
    }
    /**
     * 用用户id获取fd
     * @param int $userId
     * @param string $type
     * @return bool|mixed|string
     */
    public static function getUserIdByFd(int $userId, string $type = '')
    {
        $key = '_ws_' . $type . $userId;
        return CacheService::sMembers($key);
    }
}